package com.bizihang.common.security.app.authentication;

import com.bizihang.common.security.core.properties.SecurityProperties;
import com.bizihang.common.security.core.properties.server.OAuth2ClientProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * APP环境下认证成功处理器,需要第三方客户端信息和认证token生成器
 *
 * @author 毕子航 951755883@qq.com
 * @date 2018/11/7
 */
public class AppAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

	private Logger logger = LoggerFactory.getLogger(getClass());

	@Autowired
	SecurityProperties securityProperties;

	@Autowired
	protected ObjectMapper objectMapper;

	@Autowired
	private ClientDetailsService clientDetailsService;

	@Autowired
	private AuthorizationServerTokenServices authorizationServerTokenServices;

	@SuppressWarnings("unchecked")
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
										Authentication authentication) throws IOException, ServletException {
		logger.info("登录成功");
		OAuth2AccessToken token = createOauth2Token(request, authentication);
		writeToClient(response, token);
	}

	protected void writeToClient(HttpServletResponse response, Object content) throws IOException {
		response.setContentType("application/json;charset=UTF-8");
		//返回OAuth2AccessToken
		response.getWriter().write(objectMapper.writeValueAsString(content));
	}

	/**
	 * 根据请求生成Token
	 *
	 * @param request        请求
	 * @param authentication 认证后的认证信息
	 * @return
	 * @throws IOException
	 */
	protected OAuth2AccessToken createOauth2Token(HttpServletRequest request, Authentication authentication) throws IOException {
		//获取授权模块的ClientId和ClientSecret进行登陆
		String header = request.getHeader("Authorization");
		String clientId;
		String clientSecret;
		if (header == null || !header.startsWith("Basic ")) {
			//获取默认的账号密码
			OAuth2ClientProperties firstOauth2Client = securityProperties.getOauth2().getClients()[0];
			clientId = firstOauth2Client.getClientId();
			clientSecret = firstOauth2Client.getClientSecret();
		} else {
			String[] tokens = extractAndDecodeHeader(header, request);
			assert tokens.length == 2;

			clientId = tokens[0];
			clientSecret = tokens[1];
		}

		if (StringUtils.isEmpty(clientId)) {
			throw new UnapprovedClientAuthenticationException("请求头中无client信息");
		}
		//获取client
		ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);

		if (clientDetails == null) {
			throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:" + clientId);
		} else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {
			throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);
		}

		//!!!这里设置了授权类型为：custom
		TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");

		OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);

		OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);

		//根据认证生成OAuth2AccessToken
		OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
		return token;
	}

	/**
	 * 从请求中获取认证信息
	 *
	 * @param header
	 * @param request
	 * @return
	 * @throws IOException
	 */
	private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {
		byte[] base64Token = header.substring(6).getBytes("UTF-8");
		byte[] decoded;
		try {
			decoded = Base64.decode(base64Token);
		} catch (IllegalArgumentException e) {
			throw new BadCredentialsException("Failed to decode basic authentication token");
		}
		String token = new String(decoded, "UTF-8");
		int colonPosition = token.indexOf(":");
		if (colonPosition == -1) {
			throw new BadCredentialsException("Invalid basic authentication token");
		}
		return new String[]{token.substring(0, colonPosition), token.substring(colonPosition + 1)};
	}
}
